先談談於coroutine
中發生exception的時候會有什麼事情發生,先來看看一般的狀況
import kotlinx.coroutines.*
suspend fun getUsers(): List<String> {
delay(100L)
println("getUsers()")
return listOf("Alice","John")
}
suspend fun getMoreUsers(): List<String> {
delay(100L)
throw java.io.IOException("Hello")
return listOf("Tom","Jeff")
}
fun main() = runBlocking {
try {
val usersDeferred = async { getUsers() }
val moreUsersDeferred = async { getMoreUsers() }
val users = usersDeferred.await()
val moreUsers = moreUsersDeferred.await()
} catch (exception: java.io.IOException) {
println("execption...")
}
}
可以看到結果,即使進行了try catch,還是會導致appCrash,也就是在coroutine 中的exception其實會引發CancellationException
getUsers()
execption...
Exception in thread "main" java.io.IOException: Hello
at FileKt.getMoreUsers (File.kt:11)
at FileKt$getMoreUsers$1.invokeSuspend (File.kt:-1)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
接著我們透過在coroutine
的extension有提供一個builder corotuineScope
特色
- 可以抓捕內部coroutine的exception
- 會馬上取消剩下的coroutine
他的source code
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
範例:
import kotlinx.coroutines.*
suspend fun getUsers(): List<String> {
delay(100L)
println("getUsers()")
return listOf("Alice","John")
}
suspend fun getMoreUsers(): List<String> {
delay(100L)
throw java.io.IOException("Hello")
return listOf("Tom","Jeff")
}
suspend fun runHello() {
delay(300L)
println("runHello still alive...")
}
fun main() = runBlocking {
try {
coroutineScope {
val usersDeferred = async { getUsers() }
val moreUsersDeferred = async { getMoreUsers() }
val users = usersDeferred.await()
runHello()
}
} catch (exception: java.io.IOException) {
println("execption...")
}
}
也就是加上coroutineScope
後,他會有抓捕從coroutine
拋出exception的能力,也會得到這樣的log,如同我們平常執行regular function call
的流程
getUsers()
execption...
這樣應該就也比較符合我們期待,也不會導致app Crash
特色
- 會捕抓exception會不理,也不會crash
- 發生exceptoin的coroutine不會被處理,但剩下的
coroutine
會繼續執行
看一下source code
public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = SupervisorCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
如同下方的runHello即使上方遇到crash,他還是會繼續執行
import kotlinx.coroutines.*
suspend fun getUsers(): List<String> {
delay(100L)
println("getUsers()")
return listOf("Alice","John")
}
suspend fun getMoreUsers(): List<String> {
delay(100L)
throw java.io.IOException("Hello")
return listOf("Tom","Jeff")
}
suspend fun runHello() {
delay(100L)
println("runHello still alive...")
}
fun main() = runBlocking {
try {
supervisorScope {
val usersDeferred = async { getUsers() }
val moreUsersDeferred = async { getMoreUsers() }
val users = usersDeferred.await()
runHello()
}
} catch (exception: java.io.IOException) {
println("execption...")
}
}
因此這兩種有兩個不同的使用場景跟應用,所以很簡單的端看你自己的應用場景是怎麼使用,總結一下
不加scope
- 即使有try catch,也不會被捕抓,app 會crashcorotuineScope
- 會抓exception,會產生try-catch,同時停止其他corotuinesupervisorScope
- 會抓exception,不會產生try-catch,但不會停止其他的corotuine
https://waynestalk.com/kotlin-coroutine-tutorial/
https://amitshekhar.me/blog/kotlin-coroutines
https://kotlinlang.org/docs/exception-handling.html#exception-propagation